#include <winsock2.h>
#include <stdio.h>
#include "mainloop.h"
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"

#include "CPacket.h"
#include "structs.h"
#include "CChar.h"
#include "CConsole.h"
#include "CGlobal.h"
#include "CZlib.h"
#include "Utils/CProfilingWindow.h"

#ifndef max
#define max(a,b) (a>b ? a : b) 
#endif

//GetProcessMemoryInfo

#define DEFAULT_BUFLEN 8192

struct sPacket
{
	struct sockaddr_in* client;
	int size;
	char* data;
};

//for now: global variables
unsigned long * compressTable = NULL;
char ** decompressTable = NULL;
CSemaphore* packetListCntSem;
CSemaphore* packetListMutex;
std::vector<sPacket*> packetList;

DWORD WINAPI PacketHandler(LPVOID lpParam); 

SOCKET * ListenSocket;

struct Settings s;

int mainloop(SOCKET * par_ListenSocket, unsigned long * par_compressTable, char ** par_decompressTable, struct UDPdata * UDPcomm)
{
	ListenSocket = par_ListenSocket;
	compressTable = par_compressTable;
	decompressTable = par_decompressTable;

	packetListCntSem = new CSemaphore(0);
	packetListMutex = new CSemaphore(1);

	struct sockaddr_in client;
	int iResult;
	
	unsigned int socksize = sizeof(client);
	char recvbuf[DEFAULT_BUFLEN];

	sPacket* newPacket = NULL;

	memset(&s, 0, sizeof(struct Settings));

	CreateThread(NULL, NULL, PacketHandler, NULL, NULL, NULL);

	while(1)
	{
		// Gets and executes commands from the console
		UDPcomm->status = 1;
		if(CConsole::recv(UDPcomm, &s))
			return 1;


		// Recives packets
		UDPcomm->status = 2;
		socksize = sizeof(client);
		
		iResult = recvfrom(*ListenSocket, recvbuf, DEFAULT_BUFLEN, 0, (struct sockaddr *)&client, (int*)&socksize);
		
		if (iResult<=0)
		{
			unsigned char first=(unsigned char)(client.sin_addr.S_un.S_addr>>0)&0xFF;
			unsigned char second=(unsigned char)(client.sin_addr.S_un.S_addr>>8)&0xFF;
			unsigned char third=(unsigned char)(client.sin_addr.S_un.S_addr>>16)&0xFF;
			unsigned char fourth=(unsigned char)(client.sin_addr.S_un.S_addr>>24)&0xFF;
			
			if (iResult==0)
				CConsole::outTime("Connection from ip %u.%u.%u.%u closed.",first,second,third,fourth);
			else
				CConsole::outErr("Receiving a packet from ip %u.%u.%u.%u failed with error %i. Skipping handling.",first,second,third,fourth,WSAGetLastError());
			continue;
		}


		// Gets information about the character who sent the packet
		UDPcomm->status = 3;

		newPacket = new sPacket();
		newPacket->client = new struct sockaddr_in(client);
		newPacket->data = new char[iResult];
		memcpy(newPacket->data, recvbuf, iResult);
		newPacket->size = iResult;

		packetListMutex->lock();
		packetList.push_back(newPacket);
		packetListMutex->unlock();
		packetListCntSem->forceUnlock();

		newPacket = NULL;
	}
}

DWORD WINAPI PacketHandler(LPVOID lpParam)
{
	FILE * fp;

	int max = 0;
	int getCCharByIpUdpportMax = 0;
	int isUnencryptedMax = 0;
	int genGamePacketMax = 0;
	unsigned long long now;
	unsigned long long now2;
	int diff;
	unsigned long size;

	char special, recvbuf[DEFAULT_BUFLEN], buf[DEFAULT_BUFLEN], output[DEFAULT_BUFLEN], filename[15];

	struct sockaddr_in client;
	unsigned int socksize = sizeof(client);

	CChar * schar = NULL;

	int iResult;

	sPacket* newPacket = NULL;

	while(true)
	{
		packetListCntSem->forceLock();
		packetListMutex->lock();
		newPacket = packetList.front();
		packetList.erase(packetList.begin());
		packetListMutex->unlock();

		iResult = newPacket->size;

		memcpy(recvbuf, newPacket->data, iResult);
		delete newPacket->data;

		client = *newPacket->client;
		delete newPacket->client;

		delete newPacket;

		now = clock();
		now2 = clock();

		now2 = clock();
		schar = CGlobal::getCCharByIpUdpport(client.sin_addr.S_un.S_addr,client.sin_port);
		diff = (int)(clock()-now2);
		getCCharByIpUdpportMax=max(diff,getCCharByIpUdpportMax);
		CProfilingWindow::getInstance()->setValue("getCCharByIpUdpport", "%d - Max: %d", diff, getCCharByIpUdpportMax);

		special = 0;
		switch(CPacket::isUnencrypted(recvbuf, iResult))
		{
			case 0:
				{
					// Decrypts, Verifies and Decompresses data
					if (schar==NULL)
					{
						CConsole::outErr("Received a packet for a character currently not logged on!");
						break;
					}
					if(CPacket::makePacketsReadable(recvbuf, iResult, schar, &s)){
						continue;
					}
					iResult = CZlib::decompress(recvbuf+0x1c, *((unsigned long *)(recvbuf+iResult-20)), buf, DEFAULT_BUFLEN, decompressTable);
					break;
				}
			case 1:
				{
					if(recvbuf[0x1c] == 0x0a){
						unsigned int charid;
						memcpy(&charid, &recvbuf[0x1c+0x0c], 4);

						if ((schar!=NULL) && (schar->m_id != charid))
						{
								schar->setUdpPort(0);
								schar=NULL;
						}

						if (schar==NULL)
						{
							CConsole::outDebOnly("Looking up char by charid!");
							schar = CChar::getCChar(charid, "", 0);
							if (schar != NULL)
								schar->setUdpPort(client.sin_port);
						}

						if (schar == NULL)
						{
							CConsole::outErr("Couldn't find Char with char id %d!",charid);
							break;
						}

						puts("0Ac - start");
						special = 1;
						iResult = 0;
					
					}
					break;
				}
			case 2:
				{
					continue;
				}
		}
		diff = (int)(clock()-now2);
		isUnencryptedMax=max(diff,isUnencryptedMax);
		CProfilingWindow::getInstance()->setValue("isUnencrypted", "%d - Max: %d", diff, isUnencryptedMax);

		if(!schar)
		{
			CConsole::outErr("Character not found in character list");
			continue;
		}
	
		// Logs the packet
		if(s.logenabled && iResult){
			sprintf(filename, "data\\%05hu.dat", (int)s.filenum++);
			fp = fopen(filename, "wb");
			fwrite(buf, sizeof(char), iResult, fp);
			fclose(fp);
		}

		union PacketData pd;
		pd.c = buf;

		now2 = clock();
		size = CPacket::genGamePacket(output, recvbuf, pd, iResult, special, compressTable, schar);
		diff = (int)(clock()-now2);
		genGamePacketMax=max(diff,genGamePacketMax);
		CProfilingWindow::getInstance()->setValue("genGamePacket", "%d - Max: %d", diff, genGamePacketMax);

		long long after = clock();

		max = (int)max(max,(after -now));

		CProfilingWindow::getInstance()->setValue("MLoopExecution", (int)(after - now));
		CProfilingWindow::getInstance()->setValue("MLoopExecutionMAX", (int)(max));

		if(special)
			puts("0Ac - end");
		if(size == 0)
			continue;

		// Send response to client
		iResult = sendto(*ListenSocket, output, size, 0, (struct sockaddr *)&client, socksize);

	}
}
